package t_03_aio;

import lombok.Getter;
import lombok.SneakyThrows;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * https://www.jianshu.com/p/2d33ad3e89a6
 * https://www.bilibili.com/video/av43425053
 * https://segmentfault.com/a/1190000020364149
 */
@Getter
public class AIOServer {
    AsynchronousServerSocketChannel serverChannel;

    public static void main(String[] args) {
        new AIOServer(6666);
    }

    @SneakyThrows
    public AIOServer(int port) {
        ExecutorService es = Executors.newFixedThreadPool(4); // 初始化线程池
        AsynchronousChannelGroup threadGroup = AsynchronousChannelGroup.withCachedThreadPool(es, 1);
        // 打开一个 ServerSocket 通道并获取 AsynchronousServerSocketChannel 实例
        serverChannel = AsynchronousServerSocketChannel.open(threadGroup);
        // 绑定需要监听的端口到 serverSocketChannel
        serverChannel.bind(new InetSocketAddress(port));
        System.out.printf("server starting at port %d ...%n", port);
        // 监听客户端连接,但在 AIO，每次 accept 只能接收一个 client，所以需要在处理逻辑种再次调用 accept 用于开启下一次的监听，类似于链式调用
        // 实现一个 CompletionHandler 回调接口 handler，之后需要在 handler 的实现中处理连接请求和监听下一个连接、数据收发以及通信异常
        serverChannel.accept(this, new AioHandler());

        // 阻塞程序，防止被 GC 回收
        // 由于 serverSocketChannel.accept(null, handler); 是一个异步方法，调用会直接返回，
        // 为了让子线程能够有时间处理监听客户端的连接会话，
        // 这里通过让主线程休眠一段时间(当然实际开发一般不会这么做)以确保应用程序不会立即退出。
        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
    }

    static class AioHandler implements CompletionHandler<AsynchronousSocketChannel, AIOServer> { // client 连接后的回调函数
        @SneakyThrows
        @Override
        public void completed(AsynchronousSocketChannel result, AIOServer attachment) {
            // 继续监听下一个连接请求
            attachment.getServerChannel().accept(attachment, this); // 处理下一次的 client 连接。类似链式调用
            System.out.printf("客户端 %s 已连接！%n", result.getRemoteAddress());

            // 给客户端发送数据并等待发送完成
            // result.write(ByteBuffer.wrap("From Server:Hello i am server".getBytes())).get();
            // ByteBuffer readBuffer = ByteBuffer.allocate(128);
            // 阻塞等待客户端接收数据
            // result.read(readBuffer).get();
            // System.out.println(new String(readBuffer.array()));

            doRead(result); // 执行业务逻辑
        }

        /**
         * 读取 client 发送的消息打印到控制台
         * AIO 中 OS 已经帮助我们完成了 read 的 IO 操作，所以我们直接拿到了读取的结果
         */
        private void doRead(AsynchronousSocketChannel clientChannel) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // 从 client 读取数据，在我们调用 clientChannel.read() 之前 OS，已经帮我们完成了 IO 操作
            // 我们只需要用一个缓冲区来存放读取的内容即可
            clientChannel.read(
                    buffer,   // 用于数据中转缓冲区
                    buffer,   // 用于存储 client 发送的数据的缓冲区
                    new CompletionHandler<>() {
                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            System.out.printf("接收数据大小：%d byte，缓冲区大小：%d byte。\t", result, attachment.capacity());
                            attachment.flip(); // 移动 limit 位置
                            // 读取 client 端数据
                            System.out.println("from client: " + new String(attachment.array(), StandardCharsets.UTF_8));

                            // 向 client 端写数据
                            doWrite(clientChannel);
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            System.out.println("出错了：" + exc.getMessage());
                        }
                    }
            );
        }

        @SneakyThrows
        private void doWrite(AsynchronousSocketChannel clientChannel) {
            // 向 client 发送数据，clientChannel.write() 是一个异步调用，该方法执行后会通知
            // OS 执行写的 IO 操作，会立即返回
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            System.out.print("请回答客户端消息：");
            Scanner str = new Scanner(System.in);
            String line = str.nextLine();
            buffer.put(line.getBytes(StandardCharsets.UTF_8));
            buffer.flip();
            // clientChannel.write(buffer);
            clientChannel.write(buffer).get(); // 给客户端发送数据并等待发送完成，行阻塞，直到 OS 写操作完成
        }

        @Override
        public void failed(Throwable exc, AIOServer attachment) { // 异常处理逻辑
            System.out.println("出错了：" + exc.getMessage());
        }
    }
}
